package jwlee.testthread;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

public class DownloadManager {

	static final long unitSize = 100 * 1024; // 分配给每个下载线程的字节数

	public static void main(String[] args) throws IOException {
		args = new String[] { "http://127.0.0.1:8282/mzjwebapp/mzj/test.jsp",
				"c:\\rfc2068.txt" };
		if (args.length != 2) {
			System.out.println("Usage java DownloadManager URL file_name");
			return;
		}
		DownloadManager downloadManager = new DownloadManager();
		downloadManager.doDownload(args[0], args[1]);
	}

	public void doDownload(String remoteFileUrl, String localFileName)
			throws IOException {
		long fileSize = this.getRemoteFileSize(remoteFileUrl);

		this.createFile(localFileName, fileSize);

		long threadCount = fileSize / unitSize;

		System.out.println("启动线程"
				+ (fileSize % unitSize == 0 ? threadCount : ++threadCount)
				+ "个");

		long offset = 0;

		if (fileSize <= unitSize) { // 如果远程文件尺寸小于等于unitSize
			DownloadThread downloadThread = new DownloadThread(remoteFileUrl,
					localFileName, offset, fileSize);
			downloadThread.start();
		} else { // 如果远程文件尺寸大于unitSize
			for (int i = 1; i <= threadCount; i++) {
				DownloadThread downloadThread = new DownloadThread(
						remoteFileUrl, localFileName, offset, unitSize);
				downloadThread.start();
				offset = offset + unitSize;
			}
			if (fileSize % unitSize != 0) { // 如果不能整除，则需要再创建一个线程下载剩余字节
				DownloadThread downloadThread = new DownloadThread(
						remoteFileUrl, localFileName, offset, fileSize
								- unitSize * threadCount);
				downloadThread.start();
			}
		}
	}

	// 获取远程文件尺寸
	private long getRemoteFileSize(String remoteFileUrl) throws IOException {
		long result = 0;

		HttpURLConnection httpConnection = (HttpURLConnection) new URL(
				remoteFileUrl).openConnection();

		httpConnection.setRequestMethod("HEAD");

		for (int i = 1; i <= 10; i++) {
			String headerFieldKey = httpConnection.getHeaderFieldKey(i);
			if ("Content-Length".equalsIgnoreCase(headerFieldKey)) {
				result = Long.parseLong(httpConnection.getHeaderField(i));
				break;
			}
		}
		return result;
	}

	// 创建指定大小的文件
	private void createFile(String fileName, long fileSize) throws IOException {
		File newFile = new File(fileName);
		RandomAccessFile raf = new RandomAccessFile(newFile, "rw");
		raf.setLength(fileSize);
		raf.close();
	}

	class DownloadThread extends Thread {
		private String url = null; // 待下载的文件

		private String file = null; // 本地存储路径

		private long offset = 0; // 偏移量

		private long length = 0; // 分配给本线程的下载字节数

		public DownloadThread(String url, String file, long offset, long length) {
			this.url = url;
			this.file = file;
			this.offset = offset;
			this.length = length;
			System.out.println("偏移量=" + offset + ";字节数=" + length);
		}

		public void run() {
			try {
				HttpURLConnection httpConnection = (HttpURLConnection) new URL(
						this.url).openConnection();

				httpConnection.setRequestMethod("GET");
				httpConnection.setRequestProperty("RANGE", "bytes="
						+ this.offset + "-" + (this.offset + this.length - 1));

				BufferedInputStream bis = new BufferedInputStream(
						httpConnection.getInputStream());

				byte[] buff = new byte[1024];

				int bytesRead;
				while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
					this.writeFile(this.file, this.offset, buff, bytesRead);
					this.offset = this.offset + bytesRead;
				}
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}

		// 将字节数组以随机存取方式写入文件

		// fileName是被写入的文件

		// offset代表写入文件的位置偏移量

		// bytes是待写入的字节数组

		// realLength是实际需要写入的字节数（realLength<=bytes.length）

		private void writeFile(String fileName, long offset, byte[] bytes,
				int realLength) throws IOException {
			File newFile = new File(fileName);
			RandomAccessFile raf = new RandomAccessFile(newFile, "rw");
			raf.seek(offset);
			raf.write(bytes, 0, realLength);
			raf.close();
		}
	}
}
